iT邦幫忙

2025 iThome 鐵人賽

DAY 6
0
IT 管理

Playwright + Test Design + AI Agent:自動化測試實戰系列 第 6

第 06 天:「易筋經」的測試哲學:由內而外,層層貫通

  • 分享至 

  • xImage
  •  

傳說「易筋經」是一套以「以動入靜、伸展筋骨、強身健體、宣通氣血」為宗旨的養生之道。在自動化測試裡,我們也需要一套類似的內功心法,來確保服務的品質。今天,我們會透過以測試金字塔為指引,用 AI 協助撰寫貫通服務的測試案例。這就像是修煉「易筋經」,從最基本的筋骨(單元測試)、逐步打通經脈(整合測試)、最後整個身體(E2E測試)協同運作,達到至高境界。

內功心法:測試的「易筋經」

「測試金字塔」原則是由 Martin Fowler 在2012 提出。這個原則的核心概念強調測試案例應該合理分布在不同層級,才達到最好的測試策略。透過這樣的分布,我們可以確保自動化測試在不同層面都能夠功能是否正確或 API 和服務正常、使用者流程是否順暢。但隨著產品的特性與專案不同,我們會將測試金字塔分為不同的層級:單元測試、元件/服務整合測試、契約測試(Contract Testing)、整合測試、端點到端點測試 (End-to-End Tests)。

單元測試 (Unit Tests)

在原本的「測試金字塔原則」裡,單元測試會是程式碼中的最小單位,例如:函式或者類別,並且不會依賴於其他元件,例如:資料庫、需要檔案存取。但這樣實踐會造成單元測試案例數量很多,程式碼的邏輯功能正常,但行為不一定正常。如果產品都是以 API 串接,單元測試的單元定義延伸至「API 行為」為最小單位,例如:保證 API 在正常使用下,回傳 200。或是其他異常下,會丟出 Excetion 或 500 等行為。

應用層測試 (Service/API Tests)

包含元件/服務整合測試、契約測試(Contract Testing)、整合測試,首先,元件或服務測試,就是測試兩個元件或服務測試是否能夠正常溝通協調,例如:購物車系統、付費系統、訂單系統,當使用者按下付費後,能夠正確被引導到付費系統,並且付費成功或失敗,都會在通知訂單系統,這流程可能會涉及多個 API 的溝通。在呼叫 API 前,也會檢查 Schema 是否正確等等。在應用層測試主要是確保跨服務或是跨 API 的功能正常,並不會要求使完整的工作路徑,換句換說,我可以建立假購物資料、並且假的第三方付費系統,確保服務間的資料流與邏輯正常和異常都能夠如預期。

點到端點測試 (End-to-End Tests)

這層會包含驗收測試(Acceptance Testing)和模擬使用者從頭到尾的完整操作(UI Testing),確保他們的路徑正常且能夠順利完成。雖然在「測試金字塔原則」裡,E2E 測試的數量應該最少,因為它的執行時間最長、維護成本也最高。但這類測試卻是使用者才會遇到的案例,如果你的 E2E 測試能夠涵蓋使用者所有核心路徑,那基本上可以確保他們不會遇到不該發生的問題,使用者體驗自然會更好。

因此在一開始的時候,在專案初期,如果你的單元測試或整合測試還不夠完善,先投入資源撰寫足夠的 E2E 測試,這就像先用最外層的防護罩,保護使用者會遇到的所有路徑,即便代價是執行時間較長,且問題發生時,需要花更多時間去定位。這也是為什麼非開發人員(例如產品經理或業務)最關心的就是這個環節。他們在意的不是程式碼細節,而是「使用者能不能順利完成任務?」。隨著對系統越來越瞭解,並逐步提升其他測試的涵蓋度後,就可以將一部分 E2E 測試的責任,慢慢往下移到應用層測試和單元測試,最終形成一個完美的測試金字塔。這不僅能大幅縮短執行時間,也能在問題發生時,更快地找到根源。

招式演練:RESTful 服務的測試金字塔塔

我們將以一個建立訂單的 RESTful API 為例,探討如何從三個不同層次來編寫測試,並善用 Playwright 的強大功能來完成。

案例背景:建立訂單的 REST API

想像我們有一個訂單 API,其運作方式如下:

  • POST /v1/orders:使用 JSON 格式來發送請求並且建立訂單
  • 當訂單建立成功回傳 201 Created 狀態碼,以及包含訂單詳細資訊(id, productName, quantity, userId, createdAt)的 JSON 內容。

我們的測試目標將涵蓋:

  1. 驗證 API 回應是否正確:檢查驗證 API 的回應,例如:狀態碼是否正確、是否有正確的內容欄位和資料型態是否正確。
  2. 驗證 API 與 API 間的功能是否正常:當我們成功建立訂單會呼叫另一支 API 發送通知。
  3. 模擬使用者建立訂單是否正常。

我們會先從需求面撰寫測試情境,接著使用 Playwright 來模擬使用者建立訂單是否正常,接著驗證跨 API 到 API 間是否能夠交互溝通,最後才是檢查我們的建立訂單 API 功能是否正常。

Gherkin:服務層的測試情境

在動手寫程式碼之前,我們可以先用 Gherkin 語法寫下測試情境,讓所有相關人員都能清楚理解測試意圖。

tests/features/order-api.feature

Feature: API 契約驗證 - 建立訂單服務
  身為第三方服務,我希望透過訂單 API 建立訂單,並確保回應內容符合預期行為。

  Scenario: 呼叫 /v1/orders 並建立訂單成功
    Given 當訂單服務的 API 正常運行
     When 以產品 "iPhone 17" 和數量 1 發送 POST 請求
     Then 訂單回應該狀態碼應該是 "201 Created"
      And 回應內容應該包含訂單編號、產品名稱、數量、使用者編號、建立時間  

Playwright-bdd:實作測試情境的自動化測試

tests/steps/order-api-steps.ts

import { expect } from '@playwright/test';
import { createBdd } from 'playwright-bdd';
import { z } from 'zod';

// 定義訂單回應的資料結構,包含訂單編號、產品名稱、數量、訂購者編號與訂單建立時間
const OrderSchema = z.object({
  id: z.string().nonempty(),
  productName: z.string().nonempty(),
  quantity: z.number().int().positive(),
  userId: z.string().nonempty(),
  createdAt: z.string().datetime().or(z.string().nonempty()),
});
type OrderResponse = z.infer<typeof OrderSchema>;

// 定義一個共享的 context,用於在不同步驟間傳送資料
const { Given, When, Then } = createBdd<{ apiResponse: any }>();

// 前置條件
Given('當訂單服務的 API 正常運行', async () => {
  // 此步驟用於檢查訂單服務的 health 是正常的,並且能夠呼叫 API
});

// 執行動作
When('以產品 {string} 和數量 {int} 發送 POST 請求', async ({ api, apiResponse }, productName: string, quantity: number) => {
  // 使用 api.post 發送請求,並將回應儲存在共享變數中
  apiResponse.response = await api.post('/api/v1/orders', {
    data: { productName, quantity }
  });
});

// 確認結果
Then('回應狀態碼應為 {int} Created', async ({ apiResponse }, status: number) => {
  expect(apiResponse.response.status(), 'API 狀態碼應該是 201 Created').toBe(status);
});

Then('回應內容應該包含訂單編號、產品名稱、數量、使用者編號、建立時間 ', async ({ apiResponse }) => {
  const json = await apiResponse.response.json();
  const parsed = OrderSchema.safeParse(json);

  // 驗證資料是否符合我們定義的 Schema
  if (!parsed.success) {
    console.error(parsed.error.format());
  }
  expect(parsed.success, '回應內容應該包含訂單對應的資訊').toBe(true);

  // (可選)更進一步驗證某個欄位
  const order = parsed.data;
  expect(order.productName).toBe('iPhone 17');
  expect(order.quantity).toBe(1);
});

純 API 整合測試:直接驗證後端服務與其依賴

這個測試只使用了 playwright.request 來呼叫 API。它沒有開啟任何瀏覽器,而是直接驗證 API 的外部行為,和資料是否被正確地寫入資料庫。這就是整合測試的目的:專注於元件與服務之間的互動。通常這種整合測試,因為不需要 UI 或啟動瀏覽器,也可以直接使用原本的單元測試框架(例如:Jest)實作。

tests/api/order-integration.spec.ts

import { test, expect, APIRequestContext } from '@playwright/test';
import { z } from 'zod';
import { findOrderInDatabase, clearDatabase } from '../../src/utils/db-client';

const OrderSchema = z.object({
  id: z.string().nonempty(),
  productName: z.string().nonempty(),
  quantity: z.number().int().positive(),
  userId: z.string().nonempty(),
  createdAt: z.string().datetime().or(z.string().nonempty()),
});

let api: APIRequestContext;

test.beforeAll(async ({ playwright }) => {
  api = await playwright.request.newContext({
    baseURL: process.env.API_BASE_URL ?? 'https://your-api.example.com/api/v1',
    extraHTTPHeaders: { 'Content-Type': 'application/json' },
  });
});

test.afterAll(async () => {
  await api.dispose();
  await clearDatabase(); 
});

test('建立訂單成功:應回傳 201 且資料庫有記錄', async () => {
  const payload = { productName: 'Laptop', quantity: 1 };
  
  // ACT: 直接呼叫 API
  const res = await api.post('/orders', { data: payload });
  
  // ASSERT 1: 驗證 API 回應
  expect(res.status(), '應為 201 Created').toBe(201);
  const json = await res.json() as unknown;
  const parsed = OrderSchema.safeParse(json);
  expect(parsed.success, '回應應符合 Order 合約').toBe(true);

  // ASSERT 2: 驗證後端依賴 (資料庫)
  const orderInDb = await findOrderInDatabase(parsed.success ? parsed.data.id : '');
  expect(orderInDb).toBeTruthy();
  expect(orderInDb?.productName).toBe(payload.productName);
});

UI × API 整合測試:從使用者角度驗證完整旅程

這個測試完全從使用者的視角出發。我們只用 page.fill 和 page.click 這些瀏覽器方法,來模擬完整的使用者旅程。程式碼中沒有任何直接的 API 呼叫,但我們知道這些 UI 操作會觸發後端的 API。這就是E2E 測試:驗證從前端到後端的完整流程。

tests/api/order-notification.spec.ts 

import { test, expect } from '@playwright/test';

test('UI 建立訂單後應顯示成功提示與結果', async ({ page }) => {
  // 前置:登入流程
  await page.goto('https://your-app.example.com/login');
  await page.getByTestId('login-username').fill('user01');
  await page.getByTestId('login-password').fill('pass123');
  await page.getByTestId('login-submit').click();

  // 執行:填寫訂單並提交
  await page.goto('https://your-app.example.com/orders/new');
  await page.getByTestId('product-name').fill('Laptop');
  await page.getByTestId('quantity').fill('1');
  await page.getByTestId('submit-order').click();

  // 驗證:確認 UI 顯示與列表更新
  await expect(page.getByText('訂單建立成功')).toBeVisible();
  await expect(page.getByRole('row', { name: /Laptop/ })).toBeVisible();
});

秘笈傳授:

.prompt:Playwright-BDD 測試的「招式密語

根據上面所學習到內功知識,我們可以撰寫對應的 .prompt 我們在需要撰寫 BDD 測試步驟 (Steps) 來協助完成。這份 prompt 適用於你想要將 Gherkin 語法轉換為可執行程式碼時。它會指示 AI 根據你當前開啟的 .feature 檔案,生成對應的 steps 實作。

.github/prompts/generate-bdd-steps.prompt

根據當前打開的 Gherkin Feature File,撰寫對應的 Playwright-BDD 測試步驟(steps.ts)。
請確保程式碼使用 TypeScript,並遵循 AAA 架構。

.prompt:純 API 整合測試的「招式密語」

這份 prompt 適用於你想要直接測試 API,並驗證其與後端依賴(例如:資料庫)的互動時。它會指示 AI 撰寫一個不依賴瀏覽器的純 API 測試。

.github/prompts/api-integration-test.prompt

撰寫一個針對 RESTful API 的整合測試。
請使用 Playwright 的 `request` fixture,並撰寫對應的測試在 tests/api/ 資料夾。

我會提供 URI 和回應格式,如果沒有,可以詢問我或是確認程式碼是否有對應的 API

原則:
1. 驗證正確的狀態碼 201
2. 驗證正確的回應內容
3. 必要時驗證是否正確寫入資料庫

.prompt:UI × API 整合測試的「招式密語」

撰寫一個模擬使用者完整旅程的 E2E 測試。

我會提供一個使用 Gherkin 的 .feature 檔案,請使用 Playwright 的 `page` 物件,模擬使用者的流程。

原則:
1. 使用者能順利完成流程,如果流程不清楚,請詢問我。
2. 對於每個流程需要驗證並且檢查是否符合條件。
3. 程式碼可以撰寫在 `tests/e2e/` 資料夾。

最佳實踐:風險管理

在專案中,功能釋出的時間往往不可能將所有測試都測試完成才交付給客戶。要如何選擇測試案例,其實是測試人員需要把關的一部分,通常會根據風險高或是重要功能當作優先選擇的對象,但如果選擇的測試案例不好,則會讓關鍵功能的問題上到生產環境才知道,如果測試案例太多,則會花費許多的測試時間。

  • 如果你的「單元」定義為 API 行為,那麼高風險的單元測試將專注於核心邏輯與邊界條件。例如,驗證當訂單數量為 0、為負數或超出上限時,API 是否能正確回傳錯誤。這類測試速度快,能以極低成本覆蓋最基本的風險。
  • 應用層風險主要在於服務間的溝通與資料流。高風險的應用層測試會去驗證跨服務的關鍵流程,例如:付款成功後訂單狀態是否正確更新、訂單建立後是否成功觸發通知服務。
  • 點到端點測試來自於使用者體驗與關鍵路徑。專案初期應先投入足夠的 E2E 測試來保護使用者核心路徑,這就像是先用最外層的防護罩,保護使用者會遇到的所有路徑。

收功:今日總結

今天我們學習以測試金字塔為核心,從最底層的 API 測試開始,逐步向上建立完整的測試體系。這不僅能確保單一功能的正確性、服務的協調運作,更能保證整個服務的穩定性與品質。我們也瞭解,在現代的測試江湖中,AI 是我們最強大的輔助。透過撰寫 Gherkin 測試情境與 prompt,我們將「內功心法」傳授給 AI,讓它協助我們完成繁重的測試撰寫工作。但別忘記,AI 雖然能替代撰寫程式,但無法思考測試案例的架構和背後邏輯關係。


上一篇
第 05 天:亢龍有悔,泰極否來,否極泰來。
下一篇
第 07 天:回顧:從點到線,化零為整
系列文
Playwright + Test Design + AI Agent:自動化測試實戰8
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言